package top.lishuoboy.regex.java;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.*;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Slf4j
public class MyRegexTest {

    public static void main(String[] args) {
        printChar();
    }

    /**
     * 输出所有字符
     */
    private static void printChar() {
        Charset charset = StandardCharsets.UTF_16;
        File file = new File("asset/2.笔记/1.语法/1.5.定位符/1.5.3.2.(b) ——" + charset + "所有字符.txt");
        FileUtil.del(file);   // 删文件
        StringBuilder sb = StrUtil.builder();
        for (long i = 0; i < 1L << 16; i++) {
            byte[] bytes = ByteUtil.longToBytes(i, ByteOrder.BIG_ENDIAN);
            int maxLen = 4 * ((Long.toBinaryString(i).length() - 1) / 16 + 1);               // 转16进制的最大长度
            bytes = ArrayUtil.sub(bytes, bytes.length - maxLen / 2, bytes.length);      // 截取后2/4个字节
            String idxStr = StrUtil.fillBefore(Long.toHexString(i), '0', maxLen);   // 总次数-16进制格式
            String charStr = new String(bytes, charset);        // 最终字符
            String lineText = StrUtil.format("|{}|{}Cat{}\n", idxStr, charStr, charStr);
            sb.append(lineText);
        }
        FileUtil.appendUtf8String(sb.toString(), file);
    }

    List<String> group0List, group1List, group2List, group3List;
    Pattern pattern;
    Matcher matcher;

    /** 分组 */
    @Test
    void testGroup() {
        String content = "123ABCDEG456HIGKLMN";

        // 1.hutool方式
        // 1.1.捕获分组
        // 1.1.1.不分组（默认全是第0组）
        log.info("group0==【{}】", ReUtil.findAll("\\d[A-Z]+", content, 0));                  // [3ABCDEG, 6HIGKLMN]
//        log.info("group1==【{}】", ReUtil.findAll("\\d[A-Z]+", content, 1));                  // java.lang.IndexOutOfBoundsException: No group 1
        // 1.1.2.分组，第一个括号分组为1，依次累加
        log.info("group0==【{}】", ReUtil.findAll("(\\d)([A-Z]+)", content, 0));              // [3ABCDEG, 6HIGKLMN]
        log.info("group1==【{}】", ReUtil.findAll("(\\d)([A-Z]+)", content, 1));              // [3, 6]
        log.info("group2==【{}】", ReUtil.findAll("(\\d)([A-Z]+)", content, 2));              // [ABCDEG, HIGKLMN]
//        log.info("group3==【{}】", ReUtil.findAll("(\\d)([A-Z]+)", content, 3));              // java.lang.IndexOutOfBoundsException: No group 3
        // 1.2.非捕获分组
        log.info("group0==【{}】", ReUtil.findAll("(\\d)(?:[A-Z]+)", content, 0));             // [3ABCDEG, 6HIGKLMN]
        log.info("group1==【{}】", ReUtil.findAll("(\\d)(?:[A-Z]+)", content, 1));             // [3, 6]
//        log.info("group2==【{}】", ReUtil.findAll("(\\d)(?:[A-Z]+)", content, 2));             // java.lang.IndexOutOfBoundsException: No group 2
        // 1.3.非捕获分组-零宽断言（前后预判）
        // (?=pattern)  零宽正向先行断言。exp1(?=exp2) ：查找后面 是 exp2 的 exp1。 本例：后面   是 数字。
        // (?!pattern)  零宽负向先行断言。exp1(?!exp2) ：查找后面不是 exp2 的 exp1。 本例：后面 不是 数字。
        // (?<=pattern) 零宽正向后行断言。(?<=exp2)exp1：查找前面 是 exp2 的 exp1。 本例：前面   是 数字。
        // (?<!pattern) 零宽负向后行断言。(?<!exp2)exp1：查找前面不是 exp2 的 exp1。 本例：前面 不是 数字。
        log.info("group0==【{}】", ReUtil.findAll("\\d(?=\\d)", content, 0));                 // [1, 2, 4, 5]
        log.info("group0==【{}】", ReUtil.findAll("\\d(?!\\d)", content, 0));                 // [3, 6]
        log.info("group0==【{}】", ReUtil.findAll("(?<=\\d)\\d", content, 0));                // [2, 3, 5, 6]
        log.info("group0==【{}】", ReUtil.findAll("(?<!\\d)\\d", content, 0));                // [1, 4]
//        log.info("group1==【{}】", ReUtil.findAll("\\d(?=\\D)", content, 1));                 // java.lang.IndexOutOfBoundsException: No group 1
        // 1.4.反向引用-表达式内部用“\group”，表达式内外部用“$group”
        String contenTmp;
        // demo1 : 匹配2个连续相同的数字
        contenTmp = "2002年的第一场雪，比2000年来的更晚一些。";
        log.info("group0==【{}】", ReUtil.findAll("(\\d)\\1", contenTmp, 0));                     // [00, 00]
        log.info("result==【{}】", ReUtil.replaceAll(contenTmp, "(\\d)\\1", "（被替换值$1）"));         // 2（被替换值0）2年的第一场雪，比2（被替换值0）0年来的更晚一些。
        // demo2 : 匹配3个连续相同的数字
        log.info("group0==【{}】", ReUtil.findAll("(\\d)\\1{2}", contenTmp, 0));                  // [000]
        log.info("result==【{}】", ReUtil.replaceAll(contenTmp, "(\\d)\\1{2}", "（被替换值$1）"));      // 2002年的第一场雪，比2（被替换值0）年来的更晚一些。
        // demo3 : 匹配个位与千位相同、十位与百位相同的数字
        log.info("group0==【{}】", ReUtil.findAll("(\\d)(\\d)\\2\\1", contenTmp, 0));             // [2002]
        log.info("result==【{}】", ReUtil.replaceAll(contenTmp, "(\\d)(\\d)\\2\\1", "（被替换值$1）")); // （被替换值2）年的第一场雪，比2000年来的更晚一些。
        // demo4 : 给 连续相同的单词或数字 去重
        contenTmp = "hello hello world! @ 2024 2024";
        log.info("group0==【{}】", ReUtil.findAll("\\b(\\w+) \\1", contenTmp, 0));                 // [hello hello, 2024 2024]
        log.info("result==【{}】", ReUtil.replaceAll(contenTmp, "\\b(\\w+) \\1", "$1"));              // hello world! @ 2024

        // 2.原生java方式
        // 2.1.捕获分组
        // 2.1.1.不分组（默认全是第0组）
        group0List = new ArrayList<>();
        group1List = new ArrayList<>();
        pattern = Pattern.compile("\\d[A-Z]+");
        matcher = pattern.matcher(content);
        while (matcher.find()) {
            group0List.add(matcher.group(0));
//            group1List.add(matcher.group(1));                                                               // java.lang.IndexOutOfBoundsException: No group 1
        }
        log.info("group0==【{}】", group0List);                                                               // [3ABCDEG, 6HIGKLMN]
        // 2.1.2.分组，第一个括号分组为1，依次累加
        group0List = new ArrayList<>();
        group1List = new ArrayList<>();
        group2List = new ArrayList<>();
        group3List = new ArrayList<>();
        pattern = Pattern.compile("(\\d)([A-Z]+)");
        matcher = pattern.matcher(content);
        while (matcher.find()) {
            group0List.add(matcher.group(0));
            group1List.add(matcher.group(1));
            group2List.add(matcher.group(2));
//            group3List.add(matcher.group(3));                                                             // java.lang.IndexOutOfBoundsException: No group 3
        }
        log.info("group0==【{}】", group0List);                                                                  // [3ABCDEG, 6HIGKLMN]
        log.info("group1==【{}】", group1List);                                                                  // [3, 6]
        log.info("group2==【{}】", group2List);                                                                  // [ABCDEG, HIGKLMN]
        // 2.2.非捕获分组
        group0List = new ArrayList<>();
        group1List = new ArrayList<>();
        group2List = new ArrayList<>();
        pattern = Pattern.compile("(\\d)(?:[A-Z]+)");
        matcher = pattern.matcher(content);
        while (matcher.find()) {
            group0List.add(matcher.group(0));
            group1List.add(matcher.group(1));
//            group2List.add(matcher.group(2));                                                                   // java.lang.IndexOutOfBoundsException: No group 2
        }
        log.info("group0==【{}】", group0List);                                                                  // [3ABCDEG, 6HIGKLMN]
        log.info("group1==【{}】", group1List);                                                                  // [3, 6]
        // 2.3.非捕获分组-零宽断言（前后预判）【更多看上面的 1.3.】
        group0List = new ArrayList<>();
        group1List = new ArrayList<>();
        pattern = Pattern.compile("\\d(?=\\d)");
        matcher = pattern.matcher(content);
        while (matcher.find()) {
            group0List.add(matcher.group(0));
//            group1List.add(matcher.group(1));                                                                   // java.lang.IndexOutOfBoundsException: No group 1
        }
        log.info("group0==【{}】", group0List);                                                                  // [1, 2, 4, 5]
        // 2.4.反向引用-表达式内部用“\group”，表达式内外部用“$group”【更多看上面的 1.4.】
        // demo1 : 匹配2个连续相同的数字
        group0List = new ArrayList<>();
        contenTmp = "2002年的第一场雪，比2000年来的更晚一些。";
        pattern = Pattern.compile("(\\d)\\1");
        matcher = pattern.matcher(contenTmp);
        while (matcher.find()) {
            group0List.add(matcher.group(0));
        }
        log.info("group0==【{}】", group0List);                                                                   // [00, 00]
        log.info("result==【{}】", matcher.replaceAll("（被替换值$1）"));         // 2（被替换值0）2年的第一场雪，比2（被替换值0）0年来的更晚一些。
    }

    /** 修饰符 /g-全局搜索（global） */
    @Test
    void testG() {
        String content = "111222";
        // 全局搜索
        log.warn("list=={}", ReUtil.findAll("\\d{3}", content, 0));     // list==[111, 222]
        // 不全局搜索（只找第一个）
        log.warn("str=={}", ReUtil.get("\\d{3}", content, 0));      // str==111

        // 全局搜索替换
        log.warn("content=={}", ReUtil.replaceAll(content, "\\d{3}", "【替换的文本】"));               // content==【替换的文本】【替换的文本】
        // 替换第1个
        log.warn("content=={}", ReUtil.replaceFirst(Pattern.compile("\\d{3}"), content, "【替换的文本】"));   // content==【替换的文本】222
        // 全局搜索替换
        log.warn("content=={}", content.replaceAll("\\d{3}", "【替换的文本】"));                              // content==【替换的文本】【替换的文本】
        // 替换第1个
        log.warn("content=={}", content.replaceFirst("\\d{3}", "【替换的文本】"));                            // content==【替换的文本】222
    }

    /** 修饰符 /i-忽略大小写（case insensitive） */
    @Test
    void testI() {
        String content = "hello WORLD";
        // 区分大小写（默认）
        log.warn("list=={}", ReUtil.findAll("[a-z]+", content, 0));                             // list==[hello]
        // 忽略大小写 /i （2种方式）
        log.warn("list=={}", ReUtil.findAll("(?i)[a-z]+", content, 0));                                         // list==[hello, WORLD]
        log.warn("list=={}", ReUtil.findAll(Pattern.compile("[a-z]+", Pattern.CASE_INSENSITIVE), content, 0));  // list==[hello, WORLD]

        // 区分大小写替换
        log.warn("content=={}", ReUtil.replaceAll(content, "[a-z]+", "【替换的文本】"));       // content==【替换的文本】 WORLD
        // 忽略大小写替换 （2种方式）
        log.warn("content=={}", ReUtil.replaceAll(content, "(?i)[a-z]+", "【替换的文本】"));   // content==【替换的文本】 【替换的文本】
        log.warn("content=={}", ReUtil.replaceAll(content, Pattern.compile("[a-z]+", Pattern.CASE_INSENSITIVE), "【替换的文本】"));   // content==【替换的文本】 【替换的文本】
        // 区分大小写替换
        log.warn("content=={}", content.replaceAll("[a-z]+", "【替换的文本】"));                   // content==【替换的文本】 WORLD
        // 忽略大小写替换
        log.warn("content=={}", content.replaceAll("(?i)[a-z]+", "【替换的文本】"));               // content==【替换的文本】 【替换的文本】
    }

    /** 修饰符 /m-多行匹配（multiline） */
    @Test
    void testM() {
        String content = """
            111222
            111222
            """;
        // 单行匹配（默认）
        log.warn("list=={}", ReUtil.findAll(Pattern.compile("^111"), content, 0));              // list==[111]
        // 多行匹配 /i （2种方式）
        log.warn("list=={}", ReUtil.findAll("(?m)^111", content, 0));                           // list==[111, 111]
        log.warn("list=={}", ReUtil.findAll(Pattern.compile("^111", Pattern.MULTILINE), content, 0));   // list==[111, 111]

        // 单行匹配替换
        log.warn("content=={}", ReUtil.replaceAll(content, "^111", "【替换的文本】"));       // content==【替换的文本】222\n111222
        // 多行匹配替换 （2种方式）
        log.warn("content=={}", ReUtil.replaceAll(content, "(?m)^111", "【替换的文本】"));   // content==【替换的文本】222\n【替换的文本】222
        log.warn("content=={}", ReUtil.replaceAll(content, Pattern.compile("^111", Pattern.MULTILINE), "【替换的文本】"));   // content==【替换的文本】222\n【替换的文本】222
        // 单行匹配替换
        log.warn("content=={}", content.replaceAll("^111", "【替换的文本】"));                   // content==【替换的文本】222\n111222
        // 多行匹配替换
        log.warn("content=={}", content.replaceAll("(?m)^111", "【替换的文本】"));               // content==【替换的文本】222\n【替换的文本】222
    }

    /** 修饰符 /s（space）-特殊字符. 中包含换行符 \n */
    @Test
    void testS() {
        String content = """
            111222
            111222""";
        // 特殊字符. 中不包含换行符 \n（默认）
        log.warn("list=={}", ReUtil.findAll(Pattern.compile(".+"), content, 0));                    // list==[111222, 111222]
        // 特殊字符. 中 包含换行符 \n。 /s （2种方式）
        log.warn("list=={}", ReUtil.findAll("(?s).+", content, 0));                                 // list==[111222\n111222]
        log.warn("list=={}", ReUtil.findAll(Pattern.compile(".+", Pattern.DOTALL), content, 0));    // list==[111222\n111222]

        // 不加\s
        log.warn("content=={}", ReUtil.replaceFirst(Pattern.compile(".+"), content, "【替换的文本】"));                    // content==【替换的文本】\n111222
        //   加\s （2种方式）
        log.warn("content=={}", ReUtil.replaceFirst(Pattern.compile("(?s).+"), content, "【替换的文本】"));                // content==【替换的文本】
        log.warn("content=={}", ReUtil.replaceFirst(Pattern.compile(".+", Pattern.DOTALL), content, "【替换的文本】"));    // content==【替换的文本】
        // 不加\s
        log.warn("content=={}", content.replaceFirst(".+", "【替换的文本】"));                                             // content==【替换的文本】\n111222
        //   加\s （2种方式）
        log.warn("content=={}", content.replaceFirst("(?s).+", "【替换的文本】"));                                         // content==【替换的文本】
    }

    /**
     * <h1>unicode字符编码(\\u)16进制-双字节、(\x)16进制-单字节、(\0)8进制-单字节</h1>
     * <p>
     * 【相关文章】
     * <a href="https://blog.csdn.net/lishuoboy/article/details/142288208">【java基础】java的8个基本数据类型变量定义和赋值。进制前缀0x、0b、0（16进制、2进制、8进制），变量类型后缀L、F、D，字符char赋值‘\u0000‘、‘\000‘的含义总结。</a>
     */
    @Test
    void testUnicode() {
        String content = RandomUtil.BASE_CHAR_NUMBER;
        // 字符‘A’、‘0’对应的16进制、8进制
        log.warn("list=={}", ReUtil.findAll("[A0]", content, 0));                              // list==[A, 0]
        // 16进制字符-双字节
        log.warn("list=={}", ReUtil.findAll("[\\u0041\\u0030]", content, 0));                  // list==[A, 0]
        // 16进制数字-单字节，最大\xff
        log.warn("list=={}", ReUtil.findAll("[\\x41\\x30]", content, 0));                      // list==[A, 0]
        //  8进制数字-单字节，最大\0377
        log.warn("list=={}", ReUtil.findAll("[\\0101\\060]", content, 0));                     // list==[A, 0]
    }
}
